home *** CD-ROM | disk | FTP | other *** search
/ PC User 2003 September / Australian PC User - September 2003 (CD1).iso / magstuff / web / files / psp801ev.exe / Data1.cab / ScriptManager.PspScript < prev    next >
Encoding:
Text File  |  2003-06-06  |  19.7 KB  |  448 lines

  1. from JascApp import *
  2. from Tkinter import *
  3. from tkMessageBox import *
  4. from stat import *
  5.  
  6. import os.path
  7. import os
  8. import sys
  9. import glob
  10.  
  11. ScriptVersion = '1.3'
  12. # Revision log
  13. # 1.3 Added unicode support for localization
  14. # 1.2 Added additional logic relative to StartForeignWindow
  15. # 1.1 Added call to StartForeignWindow
  16. # 1.0 Removed the run and gui edit buttons, since there is a problem running nested Tk dialogs.
  17. # 0.3 Added word wrapping on the description, copyright and location fields.
  18. #     Added ability to send the script to the GUI editor
  19. # 0.2 Fixed bug in rename.  GetString was not using interactive mode.
  20. #     Added ability to run either silently or interactively
  21. # 0.1 Initial
  22.  
  23. # ScriptManager exists to help people manage and organize their scripts.
  24. # It provides the following functions:
  25. # 1. Displays all scripts found on the configured paths
  26. # 2. For the selected script, shows the author, copyright and description strings
  27. # 3. Allows Delete, Rename, Edit and Run of the selected script
  28. # 4. Can move a script to any configured directory
  29.  
  30. # I use a tuple to encapsulate everything I know about a script:
  31. # 0 - script name
  32. # 1 - script path
  33. # 2 - 1 if trusted, 0 if restricted
  34. # 3 - dictionary returned by script properties, None if not initialized
  35.  
  36. def ScriptProperties():
  37.     return {
  38.         'Author': 'Joe Fromm',
  39.         'Copyright': 'Copyright (C) 2002-2003, Jasc Software Inc., All Rights Reserved. Permission to create derivative works of this script is granted provided this copyright notice is included',
  40.         'Description': 'Manage existing script files - delete, rename, move, edit.',
  41.         'Host': 'Paint Shop Pro',
  42.         'Host Version': '8.00'
  43.         }
  44.  
  45. class ChooseDir(Toplevel):
  46.     'dialog class for selecting one of the defined file locations'
  47.  
  48.     def __init__(self, Current, Trusted, Restricted ):
  49.         Toplevel.__init__(self)
  50.         self.title( 'Move Script' )
  51.         
  52.         # mark the current dialog as the foreign window again        
  53.         App.Do( GlobalEnv, 'StartForeignWindow', { 'WindowHandle': int(self.winfo_id()) } )
  54.         
  55.         self.NewDir = Current        # on exit, this will be set to the selected dir
  56.         self.AllDirs = Restricted + Trusted # save the complete set so I can do the move later
  57.         
  58.         Label( self, text='Choose a directory to move to:' ).pack()
  59.  
  60.         ListFrame = Frame(self)
  61.         ListFrame.pack(side=TOP, expand=YES,fill=BOTH)
  62.         self.DirList = Listbox(ListFrame, height=10, width=75, selectmode=SINGLE, exportselection=FALSE)
  63.         self.DirList.pack(side=LEFT, expand=YES, fill=BOTH)
  64.         DirListScroll = Scrollbar(ListFrame, command = self.DirList.yview)
  65.         DirListScroll.pack(side=LEFT, fill=Y)
  66.         self.DirList.configure(yscrollcommand=DirListScroll.set)
  67.         self.DirList.bind("<Button-1>", self.OnSelectDir)
  68.         
  69.         ButtonFrame = Frame( self )
  70.         ButtonFrame.pack( expand=YES, fill = X)
  71.         Button(ButtonFrame, text='OK', width = 10, command=self.OnOK ).pack(side=LEFT, padx=10, pady=10)
  72.         Button(ButtonFrame, text='Cancel', width= 10, command=self.OnCancel ).pack(side=RIGHT, padx=10, pady=10)
  73.         
  74.         self.index = 0
  75.         for dir in Restricted:
  76.             text = 'Restricted: %s' % dir
  77.             self.DirList.insert( END, text )
  78.             if dir == Current:
  79.                 self.index = self.DirList.size() - 1
  80.             
  81.         for dir in Trusted:
  82.             text = 'Trusted: %s' % dir 
  83.             self.DirList.insert( END, text )
  84.             if dir == Current:
  85.                 self.index = self.DirList.size() - 1
  86.  
  87.         self.DirList.select_set(self.index)
  88.  
  89.         self.focus_set()
  90.         self.grab_set()
  91.         self.wait_window()
  92.  
  93.     def OnSelectDir( self, event ):
  94.         'just update the directory in the listbox'
  95.         self.index = self.DirList.nearest(event.y)
  96.     
  97.     def OnOK( self ):
  98.         'on OK we record the selected dir in self.NewDir'
  99.         self.NewDir = self.AllDirs[ self.index ]
  100.         self.destroy()
  101.         pass
  102.     
  103.     def OnCancel( self ):
  104.         'on cancel, just leave without setting a new dir'
  105.         self.destroy()
  106.         pass
  107.                 
  108.     
  109. class ScriptManager(Frame):
  110.     ''' Script manager dialog.  Puts list of script names in the left column,
  111.         with details and command buttons on the right hand side
  112.     '''
  113.     def __init__(self, TrustedDirs, RestrictedDirs, AllScripts):
  114.         Frame.__init__(self)
  115.         self.pack(expand=YES, fill=BOTH)
  116.         self.master.title('Script Manager Version %s' % ScriptVersion )
  117.  
  118.         self.TrustedDirs = TrustedDirs
  119.         self.RestrictedDirs = RestrictedDirs
  120.         self.AllScripts = AllScripts
  121.         self.index = 0
  122.         
  123.         UpperFrame = Frame(self)
  124.         UpperFrame.pack(side=TOP, expand=YES, fill=BOTH)
  125.         # Add a label field telling the user what to do.
  126.         Label(UpperFrame, text= 'Select a script to work with:',
  127.                justify=LEFT).pack(side=TOP, expand=NO, anchor=W)
  128.  
  129.         # left side of the dialog is a listbox containing the scripts
  130.         self.ScriptList = Listbox(UpperFrame, height=15, width=35, selectmode=SINGLE, exportselection=FALSE)
  131.         self.ScriptList.pack(side=LEFT, expand=YES, fill=BOTH)
  132.         ScriptListScroll = Scrollbar(UpperFrame, command = self.ScriptList.yview)
  133.         ScriptListScroll.pack(side=LEFT, fill=Y)
  134.         self.ScriptList.configure(yscrollcommand=ScriptListScroll.set)
  135.  
  136.         # The user can select a script by clicking on it
  137.         self.ScriptList.bind("<Button-1>", self.OnSelectScript)
  138.  
  139.         # add command buttons
  140.         CommandFrame = Frame(UpperFrame)
  141.         CommandFrame.pack(side=RIGHT)
  142.         Button(CommandFrame, text='Delete', command=self.OnDelete, width = 20 ).pack(side=TOP, padx=10, pady=5)
  143.         Button(CommandFrame, text='Rename', command=self.OnRename, width = 20 ).pack(side=TOP, padx=10, pady=5)
  144.         Button(CommandFrame, text='Move', command=self.OnMoveScript, width = 20 ).pack(side=TOP, padx=10, pady=5)
  145.         
  146. ##        Button(CommandFrame, text='Run Silent', command=self.OnRunSilent, width = 20 ).pack(side=TOP, padx=10, pady=5)
  147. ##        Button(CommandFrame, text='Run Interactive', command=self.OnRunInteractive, width = 20 ).pack(side=TOP, padx=5, pady=5)
  148.         Button(CommandFrame, text='Text Edit', command=self.OnSourceEdit, width = 20 ).pack(side=TOP, padx=10, pady=5)
  149. ##        Button(CommandFrame, text='Edit w/PSP', command=self.OnGUIEdit, width = 20 ).pack(side=TOP, padx=10, pady=5)
  150.         
  151.         # Put the details on the bottom
  152.         InfoFrame = Frame(self)
  153.         InfoFrame.pack(side=TOP, anchor=W, expand=YES, fill=BOTH)
  154.         self.Author = Label(InfoFrame, text='Author:', width=80, anchor=W)
  155.         self.Author.pack(side=TOP, padx = 5, expand=YES, fill=X)
  156.         self.Copyright = Label(InfoFrame, text='Copyright:', width = 80,
  157.                                height=2, anchor=W, justify=LEFT)
  158.         self.Copyright.pack(side=TOP, padx = 5, expand=YES, fill=X)
  159.         self.Description = Label(InfoFrame, text='Description:', width=80,
  160.                                  height=2, anchor=W, justify=LEFT)
  161.         self.Description.pack(side=TOP, padx = 5, expand=YES, fill=X)
  162.         self.Location = Label( InfoFrame, text='Location:', width = 80,
  163.                                height=2,anchor=W, justify=LEFT)
  164.         self.Location.pack(side=TOP, padx = 5, expand=YES, fill=X)
  165.         self.Trusted = Label( InfoFrame, text='Trusted:', width = 80, anchor=W)
  166.         self.Trusted.pack(side=TOP, padx = 5, expand=YES, fill=X)
  167.         
  168.         self.Description.bind("<Configure>", self.UpdateWidth)
  169.         self.Copyright.bind("<Configure>", self.UpdateWidth)
  170.         self.Location.bind("<Configure>", self.UpdateWidth)
  171.         
  172.         # now drop the names into the list box
  173.         for Script in self.AllScripts:
  174.             self.ScriptList.insert(END, Script[0])
  175.  
  176.         # select the first entry            
  177.         self.ScriptList.select_set(0)
  178.         self.SelectIndex(0)
  179.         
  180.     def UpdateWidth(self, e):
  181.         'Resize the message widgets based on the width of the window (thanks to Gary Barton)'
  182.         WidgetWidth = e.widget.winfo_width() - 2*int(e.widget['bd'])
  183.         e.widget.config(wraplength = WidgetWidth)
  184.  
  185.     def OnSelectScript(self, event):
  186.         ' respond to a click event in the listbox - just determines the index and call SelectIndex'
  187.         if self.ScriptList.size() > 0:
  188.             # Find out which one is selected
  189.             self.SelectIndex( self.ScriptList.nearest(event.y) )
  190.  
  191.     def SelectIndex( self, index ):
  192.         'select a new script in the list box'
  193.         self.index = index
  194.         if self.AllScripts[index][3] is None: # lookup the properties if we don't have them already
  195.             self.GetScriptProperties( index )
  196.                 
  197.         Selected = self.AllScripts[index]
  198.         self.Author.config( text = 'Author: %s' % Selected[3]['Author'] )
  199.         self.Copyright.config( text = 'Copyright: %s' % Selected[3]['Copyright'] ) 
  200.         self.Description.config( text = 'Description: %s' % Selected[3]['Description'] )
  201.         self.Location.config( text = 'Location: %s' % Selected[1] )
  202.         self.Trusted.config( text= 'Trusted: %s' % ( ('No', 'Yes')[ Selected[2] ] ) )
  203.             
  204.  
  205.     def FullPath( self, index ):
  206.         'return the full path of a given index'
  207.         Path = os.path.join( self.AllScripts[index][1], self.AllScripts[index][0] )
  208.         Path += '.PspScript'
  209.         return Path
  210.  
  211.     def OnRename(self):
  212.         ''' prompt for a new scriptname.  I use the PSP GetString command
  213.             just because it is easier to specify a default value that way
  214.         '''
  215.         OldName = self.AllScripts[ self.index ][0]
  216.         OldPath = self.FullPath( self.index )
  217.         # end foreign window so that the GetString command can take focus
  218.         App.Do( GlobalEnv, 'StartForeignWindow', { 'WindowHandle': 0 } )
  219.         Result = App.Do( GlobalEnv, 'GetString', {
  220.             'DefaultText': OldName,
  221.             'DialogTitle': 'Rename script:',
  222.             'Prompt': 'New name:',
  223.             'MaxLength': 80,
  224.             'GeneralSettings': {
  225.                 'ExecutionMode': App.Constants.ExecutionMode.Interactive, 
  226.                 'AutoActionMode': App.Constants.AutoActionMode.Match
  227.                 }
  228.             })
  229.         # mark the current dialog as the foreign window again        
  230.         App.Do( GlobalEnv, 'StartForeignWindow', { 'WindowHandle': int(self.winfo_id()) } )
  231.         
  232.         NewName = Result[ 'EnteredText' ]
  233.         if Result[ 'OKButton' ] and NewName != OldName:
  234.             # replace the name in our AllScripts list
  235.             Current = self.AllScripts[self.index]
  236.             NewScript = ( NewName, Current[1], Current[2], Current[3] )
  237.             self.AllScripts[ self.index ] = NewScript
  238.             
  239.             # replace the entry in the list box
  240.             self.ScriptList.delete( self.index )
  241.             self.ScriptList.insert( self.index, NewName )
  242.             self.ScriptList.select_set(self.index)
  243.  
  244.             # finally rename the file on disk
  245.             NewPath = os.path.join( self.AllScripts[self.index][1], NewName )
  246.             NewPath += '.PspScript'
  247.             os.rename( OldPath, NewPath )
  248.     
  249.     def OnDelete(self):
  250.         'delete the current selected script'
  251.         # better confirm it
  252.         index = self.index
  253.  
  254.         mode = os.stat( self.FullPath( index ) )
  255.         if mode[0] & S_IWRITE:
  256.             MakeWriteable = 0
  257.             prompt = 'OK to delete script %s?' % self.AllScripts[index][0]
  258.         else:
  259.             MakeWriteable = 1
  260.             prompt = 'OK to delete readonly script %s?'  % self.AllScripts[index][0]
  261.             
  262.         # end foreign window so that the GetString command can take focus
  263.         App.Do( GlobalEnv, 'StartForeignWindow', { 'WindowHandle': 0 } )
  264.         if askyesno( 'Delete Script', prompt ):
  265.             # remove it from disk
  266.             path = self.FullPath( index )
  267.             if MakeWriteable:
  268.                 os.chmod( path, S_IMODE(mode[0]) | S_IWRITE )
  269.             os.remove( path )            
  270.  
  271.             self.AllScripts.remove( self.AllScripts[index] ) # remove it from our list
  272.             self.ScriptList.delete(index)   # remove it from the list box
  273.             index = max(0, index-1)         # move the selection up
  274.             self.SelectIndex( index )       # select the next script
  275.             self.ScriptList.select_set(index)
  276.         # mark the current dialog as the foreign window again        
  277.         App.Do( GlobalEnv, 'StartForeignWindow', { 'WindowHandle': int(self.winfo_id()) } )
  278.  
  279.     def OnMoveScript(self):
  280.         ''' bring up a list box with all of the configured directories and let the
  281.             user designate a new directory for the script
  282.         '''
  283.         DirDlg = ChooseDir( self.AllScripts[ self.index ][1], self.TrustedDirs, self.RestrictedDirs )
  284.  
  285.         # mark the current dialog as the foreign window again        
  286.         App.Do( GlobalEnv, 'StartForeignWindow', { 'WindowHandle': int(self.winfo_id()) } )
  287.         if DirDlg.NewDir != self.AllScripts[ self.index ][1]:
  288.             # we are moving the file
  289.             SrcPath = self.FullPath( self.index )   # where it starts
  290.             
  291.             DstPath = os.path.join( DirDlg.NewDir, self.AllScripts[ self.index ][0] ) # where it goes
  292.             DstPath += '.PspScript'
  293.  
  294.             # update the list            
  295.             Current = self.AllScripts[self.index]
  296.             NewScript = ( Current[0], DirDlg.NewDir, Current[2], Current[3] )
  297.             self.AllScripts[ self.index ] = NewScript
  298.            
  299.             os.rename( SrcPath, DstPath )
  300.         
  301.     def OnSourceEdit(self):
  302.         'launch the configured source editor on the selected file'
  303.         path = '"%s"' % self.FullPath( self.index )
  304.         os.spawnl( os.P_NOWAIT, SourceEditor, SourceEditor, path )  
  305.     
  306.     def OnGUIEdit(self):
  307.         'launch the PSP editor on the current file'
  308.         App.Do( GlobalEnv, 'EditScript', {
  309.             'ScriptPath': self.FullPath( self.index ),
  310.             'GeneralSettings': {
  311.                 'ExecutionMode': App.Constants.ExecutionMode.Silent, 
  312.                 'AutoActionMode': App.Constants.AutoActionMode.Match
  313.                 }
  314.             })
  315.         
  316.     def OnRunInteractive(self):
  317.         ''' run the current script.  This is not intended to be a good way to launch
  318.             scripts, but having the option here makes it easier to determine if a script
  319.             should be kept or deleted.
  320.             This variant runs interactively.  
  321.         '''
  322.         App.Do( GlobalEnv, 'RunScript', {
  323.             'FileName': self.FullPath( self.index ),
  324.             'ScriptExecutionMode': App.Constants.ExecutionMode.Interactive, 
  325.             'GeneralSettings': {
  326.                 'ExecutionMode': App.Constants.ExecutionMode.Silent, 
  327.                 'AutoActionMode': App.Constants.AutoActionMode.Match
  328.                 }
  329.             })
  330.         
  331.     def OnRunSilent(self):
  332.         ''' run the current script.  This is not intended to be a good way to launch
  333.             scripts, but having the option here makes it easier to determine if a script
  334.             should be kept or deleted.
  335.             This variant runs silently.  
  336.         '''
  337.         App.Do( GlobalEnv, 'RunScript', {
  338.             'FileName': self.FullPath( self.index ),
  339.             'ScriptExecutionMode': App.Constants.ExecutionMode.Silent, 
  340.             'GeneralSettings': {
  341.                 'ExecutionMode': App.Constants.ExecutionMode.Silent, 
  342.                 'AutoActionMode': App.Constants.AutoActionMode.Match
  343.                 }
  344.             })
  345.         
  346.     def GetScriptProperties( self, index ):
  347.         ''' open the script file from disk and extract the data from the script properties
  348.             method.  Updates the self.AllScripts member to include the properties
  349.         '''
  350.         script = self.AllScripts[index]
  351.         sname = self.FullPath( index )
  352.         file = open( sname, 'r' )
  353.         ScriptContents = ''
  354.         lines = file.readlines()
  355.         file.close()
  356.         for line in lines:
  357.             trimmed = line.rstrip()
  358.             trimmed += '\n'
  359.             ScriptContents += trimmed
  360.  
  361.         # load the script and try executing the script properties method        
  362.         try:
  363.             gn = {}
  364.             ln = {}
  365.             Props = {}
  366.             Props[ 'Author' ] = 'Unknown'
  367.             Props[ 'Copyright' ] = 'Unknown'
  368.             Props[ 'Description' ] = 'Unknown'
  369.             exec( ScriptContents, gn, ln )
  370.             if ln.has_key( 'ScriptProperties' ):
  371.                 Props = ln[ 'ScriptProperties' ]()
  372.             else:
  373.                 raise ValueError
  374.         except:
  375.             print 'Unable to determine script properties for %s' % script[0]
  376.             
  377.         NewEntry = ( script[0], script[1], script[2], Props )
  378.         self.AllScripts[index] = NewEntry            
  379.     
  380. def Do(Environment):
  381.     global GlobalEnv
  382.     global SourceEditor
  383.     
  384.     GlobalEnv = Environment
  385.     
  386.     # start by getting all the script directories.  The behavior of the file locations object
  387.     # is that for locations that include subdirectories it will return all of the subdirectories
  388.     # as well as the parent location. So there is no need for us to search recursively.
  389.     DirSet = App.Do( Environment, 'ReturnFileLocations' )
  390.     SourceEditor = DirSet[ 'PythonSourceEditor'][0].replace( '\\\\', '\\' )
  391.     
  392.     TrustedDirs = []
  393.     RestrictedDirs = []
  394.     AllScripts = []
  395.    
  396.     # the return value of Return file locations has double backslashes, so lets convert
  397.     # them to singles
  398.     for dir in DirSet[ 'TrustedScripts' ]:
  399.         TrustedDirs.append( dir.replace( '\\\\', '\\' ) )
  400.     for dir in DirSet[ 'RestrictedScripts' ]:
  401.         RestrictedDirs.append( dir.replace( '\\\\', '\\' ) )
  402.         
  403.     AllDirs = TrustedDirs + RestrictedDirs    
  404.     for SearchDir in AllDirs:
  405.         # concatenate the dir and file spec together
  406.         SearchPath = os.path.join( SearchDir, '*.PspScript' )
  407.         
  408.         # glob will search the directory and return a list
  409.         GlobbedFiles = glob.glob( SearchPath )
  410.         
  411.         for FileSpec in GlobbedFiles:               # now iterate the list
  412.             # break the full pathspec apart            
  413.             (Path, ScriptName) = os.path.split(FileSpec)
  414.             BareName = os.path.splitext(ScriptName)[0]
  415.  
  416.             # keep track of what is restricted vs trusted            
  417.             if Path in TrustedDirs:
  418.                 Trusted = 1
  419.             else:
  420.                 Trusted = 0
  421.  
  422.             # assemble what we know about the script so far                
  423.             ScriptInfo = ( unicode(BareName), unicode(Path), Trusted, None )
  424.             
  425.             AllScripts.append( ScriptInfo )       # and add each one to our script list
  426.      
  427.     if len(AllScripts) == 0:                # going to have a problem if there aren't any scripts
  428.         App.Do(Environment,  'MsgBox', {
  429.                     'Buttons': App.Constants.MsgButtons.OK, 
  430.                     'Icon': App.Constants.MsgIcons.Stop, 
  431.                     'Text': 'No scripts found to manage', 
  432.                     })
  433.         return
  434.  
  435.     # sort the scripts by name
  436.     AllScripts.sort( lambda a, b: cmp( a[0].lower(), b[0].lower() ))
  437.   
  438.     Mgr = ScriptManager( TrustedDirs, RestrictedDirs, AllScripts )
  439.  
  440.     # tell PSP that a foreign dialog is running.  This causes PSP to do some additional
  441.     # work to keep the UI updating properly and to prevent the script window from going
  442.     # behind PSP.
  443.     App.Do( Environment, 'StartForeignWindow', { 'WindowHandle': int(Mgr.winfo_id()) } )
  444.     Mgr.mainloop()
  445.     App.Do( Environment, 'StartForeignWindow', { 'WindowHandle': 0 } )
  446.  
  447.         
  448.